`include "defines.v"
module csr (
    input  wire             clk,
    input  wire             rst,

    //interrupt 
    input  wire             timer_interrupt,
    
    // input  wire             mtip_clear,

    output wire             timer_interrupt_flag_o,
    // from id
    input  wire[11: 0]      r_addr,
    output reg [`REG_BUS]   r_data,
    input  wire             r_ena,
    // from wb
    input  wire[11: 0]      w_addr,
    input  wire[`REG_BUS]   w_data,
    input  wire             w_ena,
    //from wb
    input  wire[`REG_BUS]   exception_type,
    input  wire[`REG_BUS]   inst_addr,
    //to ctrl&if
    output wire             transfer_flag,
    output wire[`REG_BUS]   transfer_addr,

    //for difftest
    output wire[`REG_BUS]   mstatus_diff,
    output wire[`REG_BUS]   sstatus_diff,
    output wire[`REG_BUS]   mepc_diff,
    output wire[`REG_BUS]   mtvec_diff,
    output wire[`REG_BUS]   mcause_diff,
    output wire[`REG_BUS]   mip_diff,
    output wire[`REG_BUS]   mie_diff,
    output wire[`REG_BUS]   mscratch_diff,

    output wire[`REG_BUS]   exception_code_diff
);

    // exception
    wire                exception_flag;
    wire                ret_flag;
    wire [`REG_BUS]     exception_code;

    reg [`REG_BUS]      mcycle;
    wire[`REG_BUS]      mcycle_add;
    wire[`REG_BUS]      mcycle_value;

    reg [`REG_BUS]      sstatus;
    wire[`REG_BUS]      sstatus_value;
    //[3]       :MIE
    //[7]       :MPIE
    //[12:11]   :MPP
    reg [`REG_BUS]      mstatus;
    wire[`REG_BUS]      mstatus_value;
    wire                mstatus_uie_value;
    wire                mstatus_sie_value;
    wire                mstatus_mie_value;
    wire                mstatus_upie_value;
    wire                mstatus_spie_value;
    wire                mstatus_mpie_value;
    wire                mstatus_spp_value;
    wire                mstatus_mpp_value;
    wire[1:0]           mstatus_fs_value;
    wire[1:0]           mstatus_xs_value;
    wire                mstatus_mprv_value;
    wire                mstatus_sum_value;
    wire                mstatus_mxr_value;
    wire                mstatus_tvm_value;
    wire                mstatus_tw_value;
    wire                mstatus_tsr_value;     
    wire                mstatus_sd_value;
    //
    reg [`REG_BUS]      mtvec;
    wire[`REG_BUS]      mtvec_value;

    //
    reg [`REG_BUS]      mepc;
    wire[`REG_BUS]      mepc_value;

    //
    reg [`REG_BUS]      mcause;
    wire[`REG_BUS]      mcause_value;

    reg [`REG_BUS]      mip;
    wire                mip_mtip;

    wire                mip_mtip_value;
    wire[`REG_BUS]      mip_value;

    reg [`REG_BUS]      mie;
    wire[`REG_BUS]      mie_value;

    reg [`REG_BUS]      mscratch;
    wire[`REG_BUS]      mscratch_value;

    assign exception_code = (exception_type[39] == 1'b1) ? 64'h8000000000000007 :
                            (exception_type[ 5] == 1'b1) ? 64'hb                : `ZERO_WORD;    // ecall
    assign exception_flag = exception_code != 0 ? 1'b1 : 1'b0;
    assign ret_flag       = exception_type[32]  ? 1'b1 : 1'b0;

    assign transfer_flag = exception_flag | ret_flag;
    assign transfer_addr = exception_flag ? {mtvec[63: 2], 2'h0}:
                           ret_flag       ? mepc                : `ZERO_WORD;

    assign mcycle_add       = mcycle + 64'h1;
    assign mcycle_value     = (w_ena == 1'b1) && (w_addr == `CSR_MCYCLE)    ? w_data    : mcycle_add;

    assign mstatus_value    = exception_flag                                ? {mstatus_sd_value, mstatus[62:13], 2'b11, mstatus[10: 8], mstatus[3], mstatus[ 6: 4],       1'b0, mstatus[ 2: 0]}:
                              ret_flag                                      ? {mstatus_sd_value, mstatus[62:13], 2'b00, mstatus[10: 8], 1'b1      , mstatus[ 6: 4], mstatus[7], mstatus[ 2: 0]}:
                              (w_ena == 1'b1) && (w_addr == `CSR_MSTATUS)   ? {mstatus_sd_value, w_data[62: 0]}    : mstatus;

    assign mstatus_sd_value = (w_ena == 1'b1) && (w_addr == `CSR_MSTATUS) && (w_data[16:15] == 2'b11 || w_data[14:13] == 2'b11) ? 1'b1 :
                              (w_ena == 1'b1) && (w_addr == `CSR_MSTATUS) && (w_data[16:15] == 2'b00 && w_data[14:13] == 2'b00) ? 1'b0 : mstatus[63];

    assign sstatus_value    = (w_ena == 1'b1) && (w_addr == `CSR_MSTATUS) && (w_data[16:15] == 2'b11 || w_data[14:13] == 2'b11) ? 64'h8000000000006000 :
                              (w_ena == 1'b1) && (w_addr == `CSR_MSTATUS) && (w_data[16:15] == 2'b00 && w_data[14:13] == 2'b00) ? 64'h0 : sstatus;

    assign mtvec_value      = (w_ena == 1'b1) && (w_addr == `CSR_MTVEC)     ? w_data    : mtvec;
    
    assign mepc_value       = exception_flag                                ? inst_addr :
                              (w_ena == 1'b1) && (w_addr == `CSR_MEPC)      ? w_data    : mepc;
    
    assign mcause_value     = exception_flag                                ? exception_code :
                              (w_ena == 1'b1) && (w_addr == `CSR_MCAUSE)    ? w_data         : mcause;

    assign mip_mtip         = mip[7];
    // assign mip_mtip_value   = timer_interrupt == 1'b1 ? 1'b1 :
    //                           mtip_clear == 1'b1      ? 1'b0 : mip_mtip;
    assign mip_mtip_value = timer_interrupt;
    assign mip_value        = {mip[63: 8], mip_mtip_value, mip[ 6: 0]};

    assign mie_value        = (w_ena == 1'b1) && (w_addr == `CSR_MIE)       ? w_data    : mie;

    assign mscratch_value   = (w_ena == 1'b1) && (w_addr == `CSR_MSCRATCH)  ? w_data    : mscratch;
    //中断
    assign timer_interrupt_flag_o = mstatus[3] & mie[7] & mip[7];

    always @(*) begin
        if (rst == 1'b1) begin
            r_data = `ZERO_WORD;
        end else if (r_ena == 1'b1) begin
            case (r_addr)
                `CSR_MCYCLE:    begin
                    r_data = mcycle;
                end
                `CSR_MSTATUS:   begin
                    r_data = mstatus;
                end
                `CSR_MTVEC:     begin
                    r_data = mtvec;
                end
                `CSR_MEPC:      begin
                    r_data = mepc;
                end
                `CSR_MCAUSE:    begin
                    r_data = mcause;
                end
                `CSR_MSCRATCH:  begin
                    r_data = mscratch; 
                end
                default: begin
                    r_data = `ZERO_WORD;
                end
            endcase
        end else begin
            r_data = `ZERO_WORD; 
        end
    end

    always @(posedge clk)begin
        if ( rst == 1'b1 )begin
            mcycle  <= `ZERO_WORD;
            mstatus <= {51'h0, 2'b11, 11'b0};
            sstatus <= `ZERO_WORD;
            mtvec   <= `ZERO_WORD;
            mepc    <= `ZERO_WORD;
            mcause  <= `ZERO_WORD;
            mip     <= `ZERO_WORD;
            mie     <= `ZERO_WORD;
            mscratch<= `ZERO_WORD;
        end
        else begin
            mcycle  <= mcycle_value;
            mstatus <= mstatus_value;
            sstatus <= sstatus_value;
            mtvec   <= mtvec_value;
            mepc    <= mepc_value;
            mcause  <= mcause_value;
            mip     <= mip_value;
            mie     <= mie_value;
            mscratch<= mscratch_value;
        end
    end

    assign mstatus_diff         = mstatus;
    assign sstatus_diff         = sstatus;
    assign mepc_diff            = mepc;
    assign mtvec_diff           = mtvec;
    assign mcause_diff          = mcause;
    assign mip_diff             = 64'h0;
    assign mie_diff             = mie;
    assign mscratch_diff        = mscratch;
    assign exception_code_diff  = exception_code;
endmodule
